import hashlib
import os
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Dict


@dataclass
class PDMColumn:
    oid: str
    name: str
    code: str
    data_type: str
    default: str
    length: str
    mandatory: str
    comment: str


@dataclass
class PDMTable:
    tid: str
    t_name: str
    t_code: str
    t_objectId: str
    t_creationDate: str
    t_creator: str
    t_modificationDate: str
    t_modifier: str

    columns: List[PDMColumn] = field(default_factory=list)


@dataclass
class PDMPhysicalDiagram:
    mid: str
    m_name: str
    m_code: str
    phy_id: str = ''
    phy_name: str = ''
    full_name: str = ''
    phy_code: str = ''
    tables: List[str] = field(default_factory=list)  # 保存表的 ID 集合


@dataclass
class PDMFile:
    name: str                             # 文件名称（文件名，不包含路径）
    package_name: str = ''                # Package 的名称
    database_type: str = ''               # 数据库类型（从 XML 中获取，假定在 a:DBMS 节点）
    file_md5: str = ''                    # 文件 MD5 值
    file_modify_time: str = ''            # 文件修改时间
    file_size: int = 0                    # 文件大小（字节数）
    tables: Dict[str, PDMTable] = field(default_factory=dict)
    physical_diagrams: List[PDMPhysicalDiagram] = field(default_factory=list)


class PDMParser:
    def __init__(self):
        self.ns = {
            'a': 'attribute',
            'c': 'collection',
            'o': 'object'
        }

    def parse(self, filename: str, with_columns: bool = False) -> PDMFile:
        # 获取文件属性信息
        file_size = os.path.getsize(filename)
        file_modify_time = datetime.fromtimestamp(os.path.getmtime(filename)).strftime("%Y-%m-%d %H:%M:%S")
        with open(filename, "rb") as f:
            file_bytes = f.read()
            file_md5 = hashlib.md5(file_bytes).hexdigest()

        # 解析 XML 文件
        tree = ET.parse(filename)
        root = tree.getroot()

        # 构造 PDMFile 对象，同时保存文件属性信息
        pdm = PDMFile(name=os.path.basename(filename))
        pdm.file_size = file_size
        pdm.file_modify_time = file_modify_time
        pdm.file_md5 = file_md5

        # 尝试从 XML 中查找数据库类型（假设在 a:DBMS 节点中）
        dbms_node = root.find('.//c:DBMS/o:Shortcut/a:Name', self.ns)
        pdm.database_type = dbms_node.text if dbms_node is not None else ''

        self.parse_tables(root, pdm)
        self.parse_physical_diagrams(root, pdm)

        # 只有需要加载列信息时才调用
        if with_columns:
            self.parse_columns(root, pdm)
        return pdm

    def parse_tables(self, root, pdm: PDMFile):
        table_nodes = root.findall('.//o:Table[@Id]', namespaces=self.ns)
        for table_node in table_nodes:
            tid = table_node.get('Id')
            name_node = table_node.find('a:Name', self.ns)
            code_node = table_node.find('a:Code', self.ns)
            ObjectID_node = table_node.find('a:ObjectID', self.ns)
            CreationDate_node = table_node.find('a:CreationDate', self.ns)
            Creator_node = table_node.find('a:Creator', self.ns)
            ModificationDate_node = table_node.find('a:ModificationDate', self.ns)
            Modifier_node = table_node.find('a:Modifier', self.ns)
            pdm.tables[tid] = PDMTable(
                tid=tid,
                t_name=name_node.text if name_node is not None else '',
                t_code=code_node.text if code_node is not None else '',
                t_objectId=ObjectID_node.text if ObjectID_node is not None else '',
                t_creationDate=CreationDate_node.text if CreationDate_node is not None else '',
                t_creator=Creator_node.text if Creator_node is not None else '',
                t_modificationDate=ModificationDate_node.text if ModificationDate_node is not None else '',
                t_modifier=Modifier_node.text if Modifier_node is not None else '',
                columns=[]  # 列信息由 parse_columns 加载（若需要）
            )

    def parse_physical_diagrams(self, root, pdm: PDMFile):
        # 查找所有具有 Id 属性的 Package 节点（o 命名空间下）
        package_nodes = root.findall('.//o:Package[@Id]', namespaces=self.ns)

        if not package_nodes:
            return

        for package_node in package_nodes:
            self.parse_package(package_node, pdm)

    def parse_package(self, package_node, pdm: PDMFile):
        # 获取 Package 的名称，并保存到 PDMFile 中
        package_mid_node = package_node.find('a:ObjectID', self.ns)
        package_name_node = package_node.find('a:Name', self.ns)
        package_code_node = package_node.find('a:Code', self.ns)
        package_name = package_name_node.text if package_name_node is not None else ''
        pdm.package_name = package_name
        # print(f'Package Name: {package_name}')

        # 在 Package 节点下查找物理图节点
        diagram_nodes = package_node.findall('./c:PhysicalDiagrams/o:PhysicalDiagram', self.ns)
        for diagram_node in diagram_nodes:
            diag_pid_node = diagram_node.find('a:ObjectID', self.ns)
            diag_name_node = diagram_node.find('a:Name', self.ns)
            diag_code_node = diagram_node.find('a:Code', self.ns)

            # 初始化用于保存表 ID 的列表
            diagram_table_ids = []
            # 查找当前物理图中的所有表引用节点
            table_refs = diagram_node.findall('.//o:Table[@Ref]', self.ns)
            for t in table_refs:
                tid = t.get('Ref')
                if tid in pdm.tables:
                    diagram_table_ids.append(tid)

            # 模块名称由 Package 名称和物理图名称组成
            full_diag_name = f'{package_name}/{diag_name_node.text}' if diag_name_node is not None else ''
            pdm.physical_diagrams.append(PDMPhysicalDiagram(
                mid=package_mid_node.text,
                m_name=package_name_node.text,
                m_code=package_code_node.text,
                phy_id=diag_pid_node.text,
                phy_name=diag_name_node.text,
                full_name=full_diag_name,
                phy_code=diag_code_node.text if diag_code_node is not None else '',
                tables=diagram_table_ids
            ))

    def parse_columns(self, root, pdm: PDMFile):
        for tid, table in pdm.tables.items():
            table_node = root.find(f".//c:Tables/o:Table[@Id='{tid}']", self.ns)
            if table_node is not None:
                column_nodes = table_node.findall('./c:Columns/o:Column', self.ns)
                for col_node in column_nodes:
                    col_oid = col_node.find('a:ObjectID', self.ns)
                    col_name = col_node.find('a:Name', self.ns)
                    col_code = col_node.find('a:Code', self.ns)
                    col_type = col_node.find('a:DataType', self.ns)
                    col_default = col_node.find('a:DefaultValue', self.ns)
                    col_length = col_node.find('a:Length', self.ns)
                    col_mandatory = col_node.find('a:Mandatory', self.ns)
                    col_comment = col_node.find('a:Comment', self.ns)
                    table.columns.append(PDMColumn(
                        oid=col_oid.text if col_oid is not None else '',
                        name=col_name.text if col_name is not None else '',
                        code=col_code.text if col_code is not None else '',
                        data_type=col_type.text if col_type is not None else '',
                        default=col_default.text if col_default is not None else '',
                        length=col_length.text if col_length is not None else '',
                        mandatory=col_mandatory.text if col_mandatory is not None else '',
                        comment=col_comment.text if col_comment is not None else '',
                    ))
